# Redis开发:spring.data.redis、连接、序列化、highlow api

# 击穿

概念:

出现在Redis作为缓存使用的,当key的过期时间来到时(1、时点来到,过期;2、LRU、LFU缓存淘汰算法失效),这时大量并发,造成并发访问数据库。

击穿

解决方案:

在key失效后,大量并发请求到达,访问Redis时,做如下逻辑处理:

  • 所有请求,setnx(),获取锁(分布式锁),只有一个请求端获得锁,并设置该锁失效时间(防止产生死锁)
  • 获得锁的请求端去访问数据库获取数据,并写入缓存key
  • 相反,未获得锁的请求端休眠一定时间

问题1:如果先获得锁的请求端挂了,那么通过设置锁的失效时间来解决

问题2:获得锁的请求端去访问数据库获取数据,该过程超时了,那么通过多线程来处理:

线程1:访问数据库获取数据

线程2:监视线程1是否取回来数据,get key 是否有数据,没有数据,则更新锁的失效时间

# 穿透

概念:

从业务接收查询的是你系统根本不存在的数据,这些数据根本没有必要到达你的DB

穿透

解决方案:

布隆过滤器:将数据库中的查询字段的所有信息,加入到布隆过滤器中,通过布隆过滤器验证请求数据中是否存在在过滤器名单中,若存在,说明数据库是有的,去访问数据库获取,若不存在,则说明数据库里也不存在,则直接返回查询数据不存在

# 雪崩

概念:

大量的key同时失效,间接造成大量的访问到达DB

雪崩

解决方案:

  • 往往产生雪崩,是由于系统日终处理,更新缓存数据,势必会造成大量key失效,这种场景下,与时点性相关,例如0点时刻做日终处理,要将昨日的缓存数据清除,将今日的缓存数据存入,这种的解决方案依照击穿的解决方案即可
  • 若是与时点性无关,为了避免同一时点或者某个较小的时间段内,大量key失效,这种的解决方案是采用随机过期时间

# 分布式锁

实现步骤:

  • setnx
  • 加入过期时间,防止死锁
  • 多线程(守护线程)延长过期时间

可以使用zookeeper 做分布式锁!

# Spring中调用Redis

# 应用实例-接口字段映射转化工具

提供统一标准的接口服务,内部调用服务接口字段映射转化;

应用场景1:

有许多的场景是,前后端分离的,前端管理数据如何渲染,后端提供数据,但是有时后端获取数据来源是第三方提供的,第三方提供的API的字段及结构与后端项目定义的字段及结构往往是不相同的,所以再此场景下后端与第三方接口字段映射转化很有必要;

应用场景2:

还有一些场景是,调用接口的参数及返回结构已确定,但是调用过程可以动态改变(函数式调用),此时需要字段映射做适配处理,对外部只是一套接口结构,但是实现调用细节可以有很多种,屏蔽这些调用细节,暴露给消费端是一套的接口结构。

逻辑设计图:

接口字段映射转化工具逻辑设计

代码实现(只是DEMO样例,以后会持续优化,最终做到API增强等功能):

pom.xml

...
<dependencies>

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-json</artifactId>
 </dependency>
 
	<dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>

</dependencies>
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

field-mapping.properties接口字段映射关系配置(将读取放入Redis中)

# key值前缀
field.mapping.prefix=transform:mapping:data:dmp:
# 输入关系映射,key中前俩个是接口编号,确定映射关系,后一个是key值,对应的value是三个元素组成的数组
field.mapping.inMap[queryxxx001~chaxunxxx001~p1]=parameter1~string~string
field.mapping.inMap[queryxxx001~chaxunxxx001~p2]=parameter2~string~string
field.mapping.inMap[queryxxx001~chaxunxxx001~pageIndex]=index~string~int
field.mapping.inMap[queryxxx001~chaxunxxx001~pageSize]=size~string~int
# 输出关系映射,key中前俩个是接口编号,确定映射关系,后一个是key值,对应的value是三个元素组成的数组
field.mapping.outMap[queryxxx001~chaxunxxx001~bianma]=code~string~string
field.mapping.outMap[queryxxx001~chaxunxxx001~message]=msg~string~string
field.mapping.outMap[queryxxx001~chaxunxxx001~zongshu]=total~int~string
field.mapping.outMap[queryxxx001~chaxunxxx001~data]=dataBefore~object~object
field.mapping.outMap[queryxxx001~chaxunxxx001~data.intVal]=dataBefore.intValBefore~int~int
field.mapping.outMap[queryxxx001~chaxunxxx001~data.stringVal]=dataBefore.stringValBefore~string~string
field.mapping.outMap[queryxxx001~chaxunxxx001~data.dateVal]=dataBefore.dateValBefore~date~date
field.mapping.outMap[queryxxx001~chaxunxxx001~list]=rows~array~array
field.mapping.outMap[queryxxx001~chaxunxxx001~list.item1]=rows.row1~string~string
field.mapping.outMap[queryxxx001~chaxunxxx001~list.item2]=rows.row2~string~string
field.mapping.outMap[queryxxx001~chaxunxxx001~list.item3]=rows.row3~string~string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

项目启动执行逻辑

package com.zzjs;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import com.zzjs.resources.FieldMapping;

/**
 * 	实现CommandLineRunner接口  
 * 	项目启动成功后执行一段代码
 * @author yangyanchao
 *
 */
@Component
public class CommandLineRunnerImpl implements CommandLineRunner {
	
	private static final Logger log = LoggerFactory.getLogger(CommandLineRunnerImpl.class);
	
	/**
	 * - 使用stringRedisTemplate redis调用的高级API
	 */
	@Autowired
    @Qualifier("sredisTemplate")
	StringRedisTemplate  sredisTemplate;
	
	@Autowired
	FieldMapping fieldMapping;

	@Override
	public void run(String... args) throws Exception {
		log.info("通过实现CommandLineRunner接口,在spring boot项目启动后打印参数");
        for (String arg : args) {
        	log.info(arg + " ");
        }
        log.info("初始化Redis数据开始。。。。。。。。。。。。。。。。");
        initRedisDataByFile();
        log.info("初始化Redis数据结束。。。。。。。。。。。。。。。。");
	}

	/**
	 * - 读取配置文件,将文件信息转化为Redis的缓存数据
	 */
	private void initRedisDataByFile() {
		// 存入输入关系映射 
		Map<String, Map<String, List<String>>> inList = new HashMap<>();
		// 存入输出关系映射 例如:{"queryxxx001:chaxunxxx001":{"bianma":["code","string","string"]}}
		Map<String, Map<String, List<String>>> outList = new HashMap<>();
		HashOperations<String, Object, Object> hots = sredisTemplate.opsForHash();
		
		// key值前缀
		String prefix = fieldMapping.getPrefix();
		// 输入关系映射 
		Map<String,String> inMap = fieldMapping.getInMap();
		// 输出关系映射
		Map<String,String> outMap = fieldMapping.getOutMap();
		
		// 将配置文件的数据转化为Redis的内存数据
		this.fileDataToRedisData(inMap, outMap, inList, outList);
		
		inList.forEach((id, inItemMap) -> {
			this.initRedisDataForItem(hots, prefix + id, inItemMap, outList.get(id));
		});
	}

	/**
	 * - 将配置文件的数据转化为Redis的内存数据
	 * @param inMap
	 * @param outMap
	 * @param inList
	 * @param outList
	 */
	private void fileDataToRedisData(Map<String, String> inMap, Map<String, String> outMap,
			Map<String, Map<String, List<String>>> inList, Map<String, Map<String, List<String>>> outList) {
		// 由单一哈希表结构Map<String, String>转化为Map<String, Map<String, List<String>>>
		this.convertData(inMap, inList);
		// 由单一哈希表结构Map<String, String>转化为Map<String, Map<String, List<String>>>
		this.convertData(outMap, outList);
	}

	/**
	 * - 由单一哈希表结构Map<String, String>转化为Map<String, Map<String, List<String>>>
	 * - 例如:
	 * - 由{"queryxxx001~chaxunxxx001~p1":"parameter1~string~string"}
	 * - 转化为{"queryxxx001:chaxunxxx001":{"p1":["parameter1","string","string"]}}
	 * @param map 单一哈希表结构Map<String, String>
	 * @return
	 */
	private void convertData(Map<String, String> map, Map<String, Map<String, List<String>>> bMap) {
		map.forEach((key, value)->{
			log.info("map的键:" + key + ">>map的值:" + value);
			List<String> keyList = Arrays.asList(key.split("~"));
			String id = keyList.get(0) + ":" + keyList.get(1);
			Map<String, List<String>> itemMap = new HashMap<>();
			if(bMap.containsKey(id)) {
				// 存在这个id,获取Map
				itemMap = bMap.get(id);
			} else {
				bMap.put(id, itemMap);
			}
			String itemKey = keyList.get(2);
			List<String> itemVal = Arrays.asList(value.split("~"));
			itemMap.put(itemKey, itemVal);
		});
	}

	/**
	 * - 单个录入Redis缓存数据
	 * @param id
	 * @param mappingIn
	 * @param mappingOut
	 */
	private void initRedisDataForItem(HashOperations<String, Object, Object> hots, String id, Map<String, List<String>> mappingIn, Map<String, List<String>> mappingOut) {
//		HashOperations<String, Object, Object> hots = sredisTemplate.opsForHash();
		// 输入关系映射 字段赋值
//		Map<String, List<String>> mappingIn = new HashMap<>();
//		mappingIn.put("p1", Arrays.asList(new String[] {"parameter1","string","string"}));
//		mappingIn.put("p2", Arrays.asList(new String[] {"parameter2","string","string"}));
//		mappingIn.put("pageIndex", Arrays.asList(new String[] {"index","string","int"}));
//		mappingIn.put("pageSize", Arrays.asList(new String[] {"size","string","int"}));
		// 输入关系映射key
		hots.putAll(id + ":in", mappingIn);
		Map<Object, Object> mapIn = hots.entries(id + ":in");
		// 打印数据
		log.info("打印输入关系映射");
		mapIn.entrySet().stream().forEach((entry) -> {
            log.info("IN-field:" + entry.getKey() + "  value:" + entry.getValue());
        });
	
		// 输出关系映射 字段赋值
//		Map<String, List<String>> mappingOut = new HashMap<>();
//		mappingOut.put("bianma", Arrays.asList(new String[] {"code","string","string"}));
//		mappingOut.put("message", Arrays.asList(new String[] {"msg","string","string"}));
//		mappingOut.put("zongshu", Arrays.asList(new String[] {"total","int","string"}));
//		
//		mappingOut.put("data", Arrays.asList(new String[] {"dataBefore","object","object"}));
//		mappingOut.put("data.intVal", Arrays.asList(new String[] {"dataBefore.intValBefore","int","int"}));
//		mappingOut.put("data.stringVal", Arrays.asList(new String[] {"dataBefore.stringValBefore","string","string"}));
//		mappingOut.put("data.dateVal", Arrays.asList(new String[] {"dataBefore.dateValBefore","date","date"}));
//		
//		mappingOut.put("list", Arrays.asList(new String[] {"rows","array","array"}));
//		mappingOut.put("list.item1", Arrays.asList(new String[] {"rows.row1","string","string"}));
//		mappingOut.put("list.item2", Arrays.asList(new String[] {"rows.row2","string","string"}));
//		mappingOut.put("list.item3", Arrays.asList(new String[] {"rows.row3","string","string"}));
		
		// 输出关系映射key
		hots.putAll(id + ":out", mappingOut);
		Map<Object, Object> mapOut = hots.entries(id + ":out");
		// 打印数据
		log.info("打印输出关系映射");
		mapOut.entrySet().stream().forEach((entry) -> {
            log.info("OUT-field:" + entry.getKey() + "  value:" + entry.getValue());
        });
	}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167

测试入口-/test/user/testlist

package com.zzjs.web.controller.tool;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.hash.Jackson2HashMapper;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import com.zzjs.common.core.controller.BaseController;
import com.zzjs.common.core.domain.AjaxResult;
import com.zzjs.common.core.domain.entity.test.BaseOut;
import com.zzjs.common.core.domain.entity.test.BeforeIn;
import com.zzjs.common.core.domain.entity.test.BeforeOut;
import com.zzjs.common.core.domain.entity.test.TargetIn;
import com.zzjs.common.core.domain.entity.test.TargetOut;
import com.zzjs.common.core.domain.entity.test.TargetOutData;
import com.zzjs.common.core.domain.entity.test.TargetOutList;
import com.zzjs.common.utils.StringUtils;
import com.zzjs.common.utils.transformmapping.TransformMappingHandle;
import com.zzjs.common.utils.transformmapping.serializer.writer.FlattenCollectionSerializerModifier;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;

/**
 * swagger 用户测试方法
 * 
 * @author yyc
 */
@Api("用户信息管理")
@RestController
@RequestMapping("/test/user")
public class TestController extends BaseController
{
	private static final Logger log = LoggerFactory.getLogger(TestController.class);
	
	@Autowired
    private TransformMappingHandle<BeforeIn, BeforeOut, TargetIn, TargetOut> transformMappingHandle;
	
	@Autowired
    ObjectMapper objectMapper;
    
    @ApiOperation("测试URL")
    @GetMapping("/testlist")
    public AjaxResult testUserList()
    {
    	// 伪造一个前端入参实体对象数据
    	BeforeIn bi = new BeforeIn();
    	bi.setP1("j1");
    	bi.setP2("j2");
    	bi.setPageIndex("1");
    	bi.setPageSize("10");
    	log.info("前端入参实体--------" + bi);
    	BaseOut bo = transformMappingHandle.transformHandleAll(bi, TargetIn.class, BeforeOut.class, (TargetIn ti) -> {
    		// 实际应该是通过第三方接口参数类TargetIn ti调用第三方接口返回目标返回实体
    		// 测试需要伪造一个目标返回实体对象数据
    		TargetOut to = new TargetOut();
    		to.setBianma(200);
    		to.setMessage("查询成功");
    		to.setZongshu(1000);
    		TargetOutData data = new TargetOutData();
    		data.setIntVal(10);
    		data.setStringVal("xxxx");
    		data.setDateVal(new Date());
    		to.setData(data);
    		List<TargetOutList> list = new ArrayList<>();
    		TargetOutList item1 = new TargetOutList();
    		item1.setItem1("x1");
    		item1.setItem2("y1");
    		item1.setItem3("z1");
    		list.add(item1);
    		TargetOutList item2 = new TargetOutList();
    		item2.setItem1("x2");
    		item2.setItem2("y2");
    		item2.setItem3("z2");
    		list.add(item2);
    		to.setList(list);
    		return to;
    	});
    	// 返回 -> 前端返回实体对象数据
    	BeforeOut boind = (BeforeOut)bo;
    	log.info("前端返回实体--------" + boind);
        return AjaxResult.success(boind);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

自定义注解-标识列表字段是否展平

package com.zzjs.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * -平展的集合自定义注解 用于标识字段是要开启平展模式的
 * @author yangyanchao
 *
 */
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) 
@Retention(RetentionPolicy.RUNTIME)
public @interface FlattenCollection {

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

定义的实体类型

package com.zzjs.common.core.domain.entity.test;

/**
 * 	用于入参实体的基类 统一入参实体类型
 * @author yangyanchao
 *
 */
public class BaseIn {
	
}

1
2
3
4
5
6
7
8
9
10
11
package com.zzjs.common.core.domain.entity.test;

/**
 * 	用于返回实体的基类 统一返回类型
 * @author yangyanchao
 *
 */
public class BaseOut {
	
}

1
2
3
4
5
6
7
8
9
10
11
package com.zzjs.common.core.domain.entity.test;

/**
 * 	测试-前端入参实体
 *
 * @author yangyc
 */
public class BeforeIn extends BaseIn
{
    private static final long serialVersionUID = 1L;

    /**
     * -参数1
     */
    private String p1;

    /**
     * -参数2
     */
    private String p2;
    
    /**
     * -当前页码
     */
    private String pageIndex;
    
    /**
     * -每页最大条数
     */
    private String pageSize;

	public String getP1() {
		return p1;
	}

	public void setP1(String p1) {
		this.p1 = p1;
	}

	public String getP2() {
		return p2;
	}

	public void setP2(String p2) {
		this.p2 = p2;
	}

	public String getPageIndex() {
		return pageIndex;
	}

	public void setPageIndex(String pageIndex) {
		this.pageIndex = pageIndex;
	}

	public String getPageSize() {
		return pageSize;
	}

	public void setPageSize(String pageSize) {
		this.pageSize = pageSize;
	}

	public static long getSerialversionuid() {
		return serialVersionUID;
	}

	@Override
	public String toString() {
		return "BeforeIn [p1=" + p1 + ", p2=" + p2 + ", pageIndex=" + pageIndex + ", pageSize=" + pageSize + "]";
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.zzjs.common.core.domain.entity.test;

import java.util.List;

import com.zzjs.common.annotation.FlattenCollection;

/**
 * 	测试-前端返回实体
 *
 * @author yangyc
 */
public class BeforeOut extends BaseOut
{
    private static final long serialVersionUID = 1L;

    /**
     * -返回码
     */
    private String code;
    
    /**
     * -返回信息
     */
    private String msg;
    
    /**
     * -总条数
     */
    private String total;
    
    /**
     * -数据实体
     */
    private BeforeOutData dataBefore;
    
    /**
     * -列表数据
     */
    @FlattenCollection
    private List<BeforeOutRows> rows;
    
	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}

	public String getTotal() {
		return total;
	}

	public void setTotal(String total) {
		this.total = total;
	}

	public List<BeforeOutRows> getRows() {
		return rows;
	}

	public void setRows(List<BeforeOutRows> rows) {
		this.rows = rows;
	}

	public static long getSerialversionuid() {
		return serialVersionUID;
	}

	public BeforeOutData getDataBefore() {
		return dataBefore;
	}

	public void setDataBefore(BeforeOutData dataBefore) {
		this.dataBefore = dataBefore;
	}

	@Override
	public String toString() {
		return "BeforeOut [code=" + code + ", msg=" + msg + ", total=" + total + ", dataBefore=" + dataBefore
				+ ", rows=" + rows + "]";
	}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package com.zzjs.common.core.domain.entity.test;

import java.util.Date;

/**
 * 	测试-前端返回实体-data
 *
 * @author yangyc
 */
public class BeforeOutData 
{
    private static final long serialVersionUID = 1L;

    /**
     * -整型类型值
     */
    private Integer intValBefore;
    
    /**
     * -字符串类型值
     */
    private String stringValBefore;
    
    /**
     * -日期类型值
     */
    private Date dateValBefore;

	public Integer getIntValBefore() {
		return intValBefore;
	}

	public void setIntValBefore(Integer intValBefore) {
		this.intValBefore = intValBefore;
	}

	public String getStringValBefore() {
		return stringValBefore;
	}

	public void setStringValBefore(String stringValBefore) {
		this.stringValBefore = stringValBefore;
	}

	public Date getDateValBefore() {
		return dateValBefore;
	}

	public void setDateValBefore(Date dateValBefore) {
		this.dateValBefore = dateValBefore;
	}

	public static long getSerialversionuid() {
		return serialVersionUID;
	}

	@Override
	public String toString() {
		return "BeforeOutData [intValBefore=" + intValBefore + ", stringValBefore=" + stringValBefore
				+ ", dateValBefore=" + dateValBefore + "]";
	}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.zzjs.common.core.domain.entity.test;

/**
 * 	测试-前端返回实体-rows
 *
 * @author yangyc
 */
public class BeforeOutRows 
{
    private static final long serialVersionUID = 1L;

    /**
     * -行1
     */
    private String row1;
    
    /**
     * -行2
     */
    private String row2;
    
    /**
     * -行3
     */
    private String row3;

	public String getRow1() {
		return row1;
	}

	public void setRow1(String row1) {
		this.row1 = row1;
	}

	public String getRow2() {
		return row2;
	}

	public void setRow2(String row2) {
		this.row2 = row2;
	}

	public String getRow3() {
		return row3;
	}

	public void setRow3(String row3) {
		this.row3 = row3;
	}

	public static long getSerialversionuid() {
		return serialVersionUID;
	}

	@Override
	public String toString() {
		return "BeforeOutRows [row1=" + row1 + ", row2=" + row2 + ", row3=" + row3 + "]";
	}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.zzjs.common.core.domain.entity.test;

/**
 * 	测试-目标入参实体
 *
 * @author yangyc
 */
public class TargetIn extends BaseIn
{
    private static final long serialVersionUID = 1L;

    /**
     * -参数1
     */
    private String parameter1;

    /**
     * -参数2
     */
    private String parameter2;
    
    /**
     * -当前页码
     */
    private Integer index;
    
    /**
     * -每页最大条数
     */
    private Integer size;


	public String getParameter1() {
		return parameter1;
	}

	public void setParameter1(String parameter1) {
		this.parameter1 = parameter1;
	}

	public String getParameter2() {
		return parameter2;
	}

	public void setParameter2(String parameter2) {
		this.parameter2 = parameter2;
	}

	public Integer getIndex() {
		return index;
	}

	public void setIndex(Integer index) {
		this.index = index;
	}

	public Integer getSize() {
		return size;
	}

	public void setSize(Integer size) {
		this.size = size;
	}

	public static long getSerialversionuid() {
		return serialVersionUID;
	}

	@Override
	public String toString() {
		return "TargetIn [parameter1=" + parameter1 + ", parameter2=" + parameter2 + ", index=" + index + ", size="
				+ size + "]";
	}


}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.zzjs.common.core.domain.entity.test;

import java.util.List;

import com.zzjs.common.annotation.FlattenCollection;

/**
 * 	测试-目标返回实体
 *
 * @author yangyc
 */
public class TargetOut  extends BaseOut
{
    private static final long serialVersionUID = 1L;

    /**
     * -返回码
     */
    private int bianma;
    
    /**
     * -返回信息
     */
    private String message;
    
    /**
     * -总条数
     */
    private Integer zongshu;
    
    /**
     * -数据
     */
    private TargetOutData data;
    
    /**
     * -列表数据
     */
    @FlattenCollection
    private List<TargetOutList> list;
    
	public int getBianma() {
		return bianma;
	}

	public void setBianma(int bianma) {
		this.bianma = bianma;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public Integer getZongshu() {
		return zongshu;
	}

	public void setZongshu(Integer zongshu) {
		this.zongshu = zongshu;
	}

	public List<TargetOutList> getList() {
		return list;
	}

	public void setList(List<TargetOutList> list) {
		this.list = list;
	}

	public static long getSerialversionuid() {
		return serialVersionUID;
	}

	public TargetOutData getData() {
		return data;
	}

	public void setData(TargetOutData data) {
		this.data = data;
	}

	@Override
	public String toString() {
		return "TargetOut [bianma=" + bianma + ", message=" + message + ", zongshu=" + zongshu + ", data=" + data
				+ ", list=" + list + "]";
	}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package com.zzjs.common.core.domain.entity.test;

import java.util.Date;

/**
 * 	测试-目标返回实体-data
 *
 * @author yangyc
 */
public class TargetOutData
{
    private static final long serialVersionUID = 1L;

    /**
     * -整型类型值
     */
    private Integer intVal;
    
    /**
     * -字符串类型值
     */
    private String stringVal;
    
    /**
     * -日期类型值
     */
    private Date dateVal;

	public Integer getIntVal() {
		return intVal;
	}

	public void setIntVal(Integer intVal) {
		this.intVal = intVal;
	}

	public String getStringVal() {
		return stringVal;
	}

	public void setStringVal(String stringVal) {
		this.stringVal = stringVal;
	}

	public Date getDateVal() {
		return dateVal;
	}

	public void setDateVal(Date dateVal) {
		this.dateVal = dateVal;
	}

	public static long getSerialversionuid() {
		return serialVersionUID;
	}

	@Override
	public String toString() {
		return "TargetOutData [intVal=" + intVal + ", stringVal=" + stringVal + ", dateVal=" + dateVal + "]";
	}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.zzjs.common.core.domain.entity.test;

/**
 * 	测试-目标返回实体-list
 *
 * @author yangyc
 */
public class TargetOutList
{
    private static final long serialVersionUID = 1L;

    /**
     * -条目1
     */
    private String item1;
    
    /**
     * -条目2
     */
    private String item2;
    
    /**
     * -条目3
     */
    private String item3;

	public String getItem1() {
		return item1;
	}

	public void setItem1(String item1) {
		this.item1 = item1;
	}

	public String getItem2() {
		return item2;
	}

	public void setItem2(String item2) {
		this.item2 = item2;
	}

	public String getItem3() {
		return item3;
	}

	public void setItem3(String item3) {
		this.item3 = item3;
	}

	public static long getSerialversionuid() {
		return serialVersionUID;
	}

	@Override
	public String toString() {
		return "TargetOutList [item1=" + item1 + ", item2=" + item2 + ", item3=" + item3 + "]";
	}

    
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

接口字段转化操作类

大概实现逻辑:将原实体对象序列化到Redis中的key(entityKey),value类型为hashs,Redis中获取相应的映射字段,变更hashs中的key名称,以及hashs中的Value类型转化,再将Redis中的entityKey反序列化为对应的目标实体对象

使用技术点:调用RedisApi,函数式编程回调函数(调用第三方接口过程是个性化的需要调用者传入回调函数),类型转化(使用工厂模式,抽象转化指令减少if-else语句)

package com.zzjs.common.utils.transformmapping;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.hash.Jackson2HashMapper;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import com.zzjs.common.core.domain.entity.test.BaseIn;
import com.zzjs.common.core.domain.entity.test.BaseOut;
import com.zzjs.common.utils.factory.OperatorFactory;
import com.zzjs.common.utils.factory.operation.base.Operation;
import com.zzjs.common.utils.transformmapping.serializer.writer.FlattenCollectionSerializerModifier;
import com.zzjs.common.utils.transformmapping.serializer.writer.FlattenNameTransformer;

/**
 * 	接口转化操作类
 * @author yangyanchao
 *
 * @param <BI>   前端入参实体
 * @param <BO>   前端返回实体
 * @param <TI>   目标入参实体
 * @param <TO>   目标返回实体
 */
@Component
public class TransformMappingHandle<BI extends BaseIn, BO extends BaseOut, TI extends BaseIn, TO extends BaseOut> {
	
	private static final Logger log = LoggerFactory.getLogger(TransformMappingHandle.class);
	
	private static final String FLATTEN_COLLECTION_PREFIX = "list:";
	
	@Autowired
    @Qualifier("sredisTemplate")
	StringRedisTemplate  sredisTemplate;

    /**
     * 	用于对象映射  将map转为对象  对象转为map
     */
    @Autowired
    ObjectMapper objectMapper;
    
    public TransformMappingHandle() {
		super();
	}

	/**
     *	启用标识
     */
    private boolean isOpen = true;
    
	public boolean isOpen() {
		return isOpen;
	}

	public void setOpen(boolean isOpen) {
		this.isOpen = isOpen;
	}
	
    /**
	 * 	接口转化总逻辑实现
	 * @param bi       前端入参实体
	 * @param tiClass  目标入参实体class
	 * @param boClass  前端返回实体class
	 * @param func     函数式编程回调函数传入TI,返回TO  apply->调用  后面可用lambda表达式写实现逻辑代码
	 * @return
	 */
    @SuppressWarnings("unchecked")
	public BaseOut transformHandleAll(BaseIn bi, Class<TI> tiClass, Class<BO> boClass, Function<TI,TO> func) {
    	FlattenCollectionSerializerModifier modifier = new FlattenCollectionSerializerModifier();
		SerializerFactory sf = BeanSerializerFactory.instance.withSerializerModifier(modifier);
		objectMapper.setSerializerFactory(sf);
		BaseOut baseOut = null;
    	if(this.isOpen) {
    		baseOut = this.transformHandle((BI)bi, tiClass, boClass, func);
    	} else {
    		baseOut =  this.transformHandle((TI)bi, func);
    	}
    	objectMapper.setSerializerFactory(BeanSerializerFactory.instance);
    	return baseOut;
    }
	
	/**
	 * 	接口转化总逻辑实现     启用转化逻辑的
	 * @param bi       前端入参实体
	 * @param tiClass  目标入参实体class
	 * @param boClass  前端返回实体class
	 * @param func     函数式编程回调函数传入TI,返回TO  apply->调用  后面可用lambda表达式写实现逻辑代码
	 * @return
	 */
	private BO transformHandle(BI bi, Class<TI> tiClass, Class<BO> boClass, Function<TI,TO> func) {
		// 通过Redis查询输入关系映射 将BI前端入参实体 转化为 TI目标入参实体
		TI ti = transformBefore(bi, tiClass);
		log.info("目标入参实体--------" + ti);
		// 函数式执行程序行为 将TI目标入参实体传入 返回TO目标返回实体
		TO to = func.apply(ti);
		// 通过Redis查询输出关系映射 将TO目标返回实体 转化为 BO前端返回实体
		log.info("目标返回实体--------" + to);
		return transformAfter(to, boClass);
	}
	
	/**
	 * 	直接调用没有转化的
	 * @param ti       目标入参实体
	 * @param tiClass  目标入参实体class
	 * @param boClass  前端返回实体class
	 * @param func     目标返回实体 TO
	 * @return
	 */
	private TO transformHandle(TI ti, Function<TI,TO> func) {
		// 函数式执行程序行为 将TI目标入参实体传入 返回TO目标返回实体
		return func.apply(ti);
	}
	
	/**
	 * 	将BI前端入参实体 转化为 TI目标入参实体
	 * @param bi    前端入参实体
	 * @return      目标入参实体
	 */
	@SuppressWarnings("unchecked")
	private TI transformBefore(BI bi, Class<TI> tiClass) {
		Jackson2HashMapper jhm = new Jackson2HashMapper(objectMapper, true);
		HashOperations<String, String, Object> hots = sredisTemplate.opsForHash();
		String in = "transform:mapping:data:dmp:queryxxx001:chaxunxxx001:in";
		String tempin = "transform:mapping:data:dmp:queryxxx001:chaxunxxx001:tempin";
		Set<String> tempinHashKeys = hots.keys(tempin);
		if(tempinHashKeys.size() > 0) {
			for(String hashKey : tempinHashKeys) {
				// 如果之前有数据则删除掉所有数据
				hots.delete(tempin, hashKey);
			}
		}
		hots.putAll(tempin,jhm.toHash(bi));
		// 通过Redis查询输入关系映射 key=transform:mapping:data:dmp:queryxxx001:chaxunxxx001:in
		Map<String, Object> mapIn = hots.entries(in);
		// 根据字段关系映射 将tempin中的字段名更改
		transformByMapping(hots, mapIn, tempin);
		Map<String, Object> map = hots.entries(tempin);
		// 打印数据
//		map.entrySet().stream().forEach((entry) -> {
//            log.info("tempin-field:" + entry.getKey() + "  value:" + entry.getValue());
//        });
		return objectMapper.convertValue(flattenCollectionDeserializers((Map<String, Object>)jhm.fromHash(map)), tiClass);
	}

	/**
	 * 	将TO目标返回实体 转化为 BO前端返回实体
	 * @param to    目标返回实体
	 * @return      前端返回实体
	 */
	@SuppressWarnings("unchecked")
	private BO transformAfter(TO to, Class<BO> boClass) {
		Jackson2HashMapper jhm = new Jackson2HashMapper(objectMapper, true);
		HashOperations<String, String, Object> hots = sredisTemplate.opsForHash();
		String out = "transform:mapping:data:dmp:queryxxx001:chaxunxxx001:out";
		String tempout = "transform:mapping:data:dmp:queryxxx001:chaxunxxx001:tempout";
		Set<String> tempoutHashKeys = hots.keys(tempout);
		if(tempoutHashKeys.size() > 0) {
			for(String hashKey : tempoutHashKeys) {
				// 如果之前有数据则删除掉所有数据
				hots.delete(tempout, hashKey);
			}
		}
		hots.putAll(tempout,jhm.toHash(to));
		// 通过Redis查询输出关系映射 key=transform:mapping:data:dmp:queryxxx001:chaxunxxx001:out
		Map<String, Object> mapOut = hots.entries(out);
		// 根据字段关系映射 将tempout中的字段名更改
		transformByMapping(hots, mapOut, tempout);
		Map<String, Object> map = hots.entries(tempout);
		// 打印数据
//		map.entrySet().stream().forEach((entry) -> {
//            log.info("tempout-field:" + entry.getKey() + "  value:" + entry.getValue());
//        });
		return objectMapper.convertValue(flattenCollectionDeserializers((Map<String, Object>)jhm.fromHash(map)), boClass);
	}

	/**
	 * 	根据字段关系映射 将hashs-key中的字段名更改
	 * @param hots
	 * @param mapIn
	 * @param string
	 */
	@SuppressWarnings({"rawtypes", "unchecked"})
	private void transformByMapping(HashOperations<String, String, Object> hots, Map<String, Object> mapping,
			String hashsKey) {
		Map<String, Object> fields = hots.entries(hashsKey);
		// 打印转化前的数据
//		fields.entrySet().stream().forEach((entry) -> {
//            log.info("field:" + entry.getKey() + "  value:" + entry.getValue());
//        });
//		log.info("---------------------------------");
		
		// 打印关系映射数据
//		mapping.entrySet().stream().forEach((entry) -> {
//            log.info("mapping:" + entry.getKey() + "  value:" + entry.getValue());
//        });
//		log.info("---------------------------------");
		
		// 实际转化操作
		for(Entry<String, Object> entry : fields.entrySet()) {
			String field = entry.getKey();
			Object value = entry.getValue();
			String suffix = "";
			String prefix = "";
			boolean isList = false;
			if(field.startsWith(FLATTEN_COLLECTION_PREFIX)) {
				isList = true;
				// 是集合字段 要去掉前后缀 取出本身
				if(field.indexOf("_") > 0) {
					suffix = "_" + field.split("_")[1];
				}
				prefix = FLATTEN_COLLECTION_PREFIX;
				FlattenNameTransformer ftf = new FlattenNameTransformer(prefix, suffix);
				field = ftf.reverse(field);
			}
			if(mapping.containsKey(field)) {
				// 获取映射关系
				List<String> mappingVal = (List<String>)mapping.get(field);
				String newField = mappingVal.get(0);
				String oldType = mappingVal.get(1);
				String newType = mappingVal.get(2);
				if(!oldType.equals(newType)) {
					// 类型不相等时才需要转化
					// 获取类型转化操作类
					Operation operation = OperatorFactory
							.getOperation(oldType + "->" + newType)
							.orElseThrow(() -> new IllegalArgumentException("Invalid Operator -> 失效的操作"));
					value = operation.apply(value);
				}
				if(isList) {
					// 添加新的字段 改名以及转化类型
					hots.put(hashsKey, prefix + newField + suffix, value);
					// 删除老的字段
					hots.delete(hashsKey, prefix + field + suffix);
				} else {
					// 添加新的字段 改名以及转化类型
					hots.put(hashsKey, newField, value);
					// 删除老的字段
					hots.delete(hashsKey, field);
				}
			} else {
				// TODO  抛出转化异常 记录日志
			}
		}
		
//		Map<String, Object> newFields = hots.entries(hashsKey);
		// 打印转化后的数据
//		newFields.entrySet().stream().forEach((entry) -> {
//            log.info("field:" + entry.getKey() + "  value:" + entry.getValue());
//        });
//		log.info("---------------------------------");
	}
	
	/**
	 * - 将平滑的集合反序列化为立体的集合
	 * @param map  将这种key -> list:list={item3_2=z2, item2_1=y1, item1_2=x2, item3_1=z1, item2_2=y2, item1_1=x1}
	 *             
	 * @return     转化为    list=[{item1=x1, item2=y1, item3=z1}, {item1=x2, item2=y2, item3=z2}]
	 */
	@SuppressWarnings("unchecked")
	private Map<String, Object> flattenCollectionDeserializers(Map<String, Object> map) {
		List<String> deleteKeys = new ArrayList<>();
		Map<String, Object> tempMap = new HashMap<>();
		for(Entry<String, Object> entry : map.entrySet()) {
			String key = entry.getKey();
			Object value = entry.getValue();
			if(key.startsWith(FLATTEN_COLLECTION_PREFIX)) {
				deleteKeys.add(key);
				String listKey = key.substring(FLATTEN_COLLECTION_PREFIX.length());
				List<Map<String, Object>> listVal = new ArrayList<>();
				Map<String, Object> listValues = (Map<String, Object>) value;
				Map<Integer, Map<String, Object>> treeMap = new TreeMap<>();
				// 将原始的map转化为有序的treeMap { 1={item1=x1, item2=y1, item3=z1}, 2={item1=x12, item2=y2, item3=z2}}
				for(Entry<String, Object> item : listValues.entrySet()) {
					String itemKey = item.getKey();
					if(itemKey.indexOf("_") > 0) {
						Object itemVal = item.getValue();
						String[] tempArr = itemKey.split("_");
						Integer index = Integer.parseInt(tempArr[1]);
						if(treeMap.containsKey(index)) {
							Map<String, Object> indexMap = treeMap.get(index);
							indexMap.put(tempArr[0], itemVal);
						} else {
							Map<String, Object> indexMap = new HashMap<>();
							indexMap.put(tempArr[0], itemVal);
							treeMap.put(index, indexMap);
						}
					}
				}
				// 有序的treeMap转化为list
				for(Entry<Integer, Map<String, Object>> treeItem : treeMap.entrySet()) {
					listVal.add(treeItem.getValue());
				}
				tempMap.put(listKey, listVal);
			}
		}
		if(deleteKeys.size() > 0) {
			for(String key : deleteKeys) {
				map.remove(key);
			}
		}
		if(tempMap.size() > 0) {
			map.putAll(tempMap);
		}
		return map;
	}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323

自定义序列化操作

由于自身的序列化不能够展平list类型的字段,所以要自实现序列化操作

展平List序列化修改器

package com.zzjs.common.utils.transformmapping.serializer.writer;

import java.util.List;

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.zzjs.common.annotation.FlattenCollection;

/**
 * -查找集合注解(FlattenCollection)并修改bean编写器
 * 
 * @author yangyanchao
 *
 */
public class FlattenCollectionSerializerModifier extends BeanSerializerModifier {
	@Override
	public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
			List<BeanPropertyWriter> beanProperties) {
		for (int i = 0; i < beanProperties.size(); i++) {
			BeanPropertyWriter writer = beanProperties.get(i);
			// 获取集合注解
			FlattenCollection annotation = writer.getAnnotation(FlattenCollection.class);
			if (annotation != null) {
				// 修改bean的编写器 指定为自定义的编写器
				beanProperties.set(i, new FlattenCollectionPropertyWriter(writer));
			}
		}
		return beanProperties;
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

展平List属性编写器

package com.zzjs.common.utils.transformmapping.serializer.writer;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;

/**
 * -自定义的集合展平编写器
 * public static class Person { 
     private String name; 
     private int age; 

     public Person(String name, int age) { 
      this.name = name; 
      this.age = age; 
     } 

     public String getName() { 
      return name; 
     } 

     public void setName(String name) { 
      this.name = name; 
     } 

     public int getAge() { 
      return age; 
     } 

     public void setAge(int age) { 
      this.age = age; 
     } 
    } 

    public static class City { 
     private String titleCity; 
     private List<Person> people; 

     public City(String title, List<Person> people) { 
      this.titleCity = title; 
      this.people = people; 
     } 

     public String getTitleCity() { 
      return titleCity; 
     } 

     public void setTitleCity(String titleCity) { 
      this.titleCity = titleCity; 
     } 

     @FlattenCollection 
     public List<Person> getPeople() { 
      return people; 
     } 

     public void setPeople(List<Person> people) { 
      this.people = people; 
     } 
    } 
    展平为:
    { 
	    "titleCity" : "Bei Jing", 
	    "personName_1" : "Tom", 
	    "personAge_1" : 18, 
	    "personName_2" : "Sean", 
	    "personAge_2" : 22 
	} 
 * @author yangyanchao
 *
 */
@SuppressWarnings("serial")
public class FlattenCollectionPropertyWriter extends BeanPropertyWriter {
	
	private final BeanPropertyWriter writer;

	public FlattenCollectionPropertyWriter(BeanPropertyWriter writer) {
		super(writer);
		this.writer = writer;
	}

	@Override
	@SuppressWarnings("rawtypes")
	public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
		Object arrayValue = writer.get(bean);
		String propertyName = writer.getName();

		// lets try and look for array and collection values
		final Iterator iterator;
		if (arrayValue != null && arrayValue.getClass().isArray()) {
			// deal with array value
			iterator = Arrays.stream((Object[]) arrayValue).iterator();
		} else if (arrayValue != null && Collection.class.isAssignableFrom(arrayValue.getClass())) {
			iterator = ((Collection) arrayValue).iterator();
		} else {
			iterator = null;
		}

		if (iterator == null) {
			// TODO: write null? skip? dunno, you gonna figure this one out
		} else {
			int index = 0;
			while (iterator.hasNext()) {
				index++;
				Object value = iterator.next();
				if (value == null) {
					// TODO: skip null values and still increment or maybe dont increment? You
					// decide
				} else {
					// TODO: OP - update your prefix/suffix here, its kinda weird way of making a
					// prefix
//					final String prefix = "list:" + value.getClass().getSimpleName().toLowerCase() + ".";
					final String prefix = "list:" + propertyName + ".";
					final String suffix = "_" + index;
					prov.findValueSerializer(value.getClass())
							.unwrappingSerializer(new FlattenNameTransformer(prefix, suffix))
							.serialize(value, gen, prov);
				}
			}
		}
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

展平名称转化器

package com.zzjs.common.utils.transformmapping.serializer.writer;

import com.fasterxml.jackson.databind.util.NameTransformer;

/**
 * -命名转化器
 * @author yangyanchao
 *
 */
public class FlattenNameTransformer extends NameTransformer {
	private final String prefix;
	private final String suffix;

	public FlattenNameTransformer(String prefix, String suffix) {
		this.prefix = prefix;
		this.suffix = suffix;
	}

	/**
	 * - 将属性名 的首字母大写
	 */
	@Override
	public String transform(String name) {
		// captial case the first letter, to prepend the suffix
        //String transformedName = Character.toUpperCase(name.charAt(0)) + name.substring(1);
		return prefix + name + suffix;
	}

	/**
	 * - 去除前缀与后缀 取出中间的
	 */
	@Override
	public String reverse(String transformed) {
		if (transformed.startsWith(prefix)) {
			String str = transformed.substring(prefix.length());
			if (str.endsWith(suffix)) {
				return str.substring(0, str.length() - suffix.length());
			}
		}
		return null;
	}

	@Override
	public String toString() {
		return "[FlattenNameTransformer('" + prefix + "','" + suffix + "')]";
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

Redis加载模板类配置信息

package com.zzjs.framework.config;

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;

/**
 * redis配置
 * 
 * @author yyc
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        serializer.setObjectMapper(mapper);

        template.setValueSerializer(serializer);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
    
    @Bean
    public StringRedisTemplate sredisTemplate(RedisConnectionFactory fc){

        StringRedisTemplate tp = new StringRedisTemplate(fc);

        tp.setHashValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
        return  tp ;
    }
    
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59